#include <iostream>
#include <ctime>
#include <cstdlib>

//#define quaternion_debug_complex
//#define quaternion_testingtimenotmemory

using namespace std;
class Quaternion;
class RealMatrix;
class Factory;
class Suicidal;
class Complex;
class ComplexMatrix;
class GenericMatrix;

class Suicidal {

	public:
		void die();

};

class GenericMatrix {

};

class Factory {

	public:
		static Quaternion * createNewQuaternion();
		static Quaternion * createFromRealMatrix(RealMatrix *);
		static Quaternion * createFromValues(double, double, double, double);
		static Quaternion * createMatrixProduct(Quaternion *, Quaternion *);
		static Quaternion * updateQuaternion(Quaternion *);
		static RealMatrix * createNewRealMatrix();
		static RealMatrix * copyRealMatrix(RealMatrix *);
		static RealMatrix * createMatrixProduct(RealMatrix *, RealMatrix *);
		static Complex * createNewComplex();
		static Complex * createNewComplex(double, double);
		static Complex * copyComplex(Complex *);
		static ComplexMatrix * createNewComplexMatrix();
		static ComplexMatrix * copyComplexMatrix(ComplexMatrix *);
		static Quaternion * createFormulaProduct(Quaternion *, Quaternion *);
		static ComplexMatrix * createCMProduct(ComplexMatrix *, ComplexMatrix *);
		static Quaternion * createCMProduct(Quaternion *, Quaternion *);

};

/*class Calc : public Suicidal {

	public:
		//I have to do this later
	private:
		//ditto
};*/

class Complex : public Suicidal {

	public:
		friend ostream & operator << (ostream& output, const Complex * p);
		Complex & operator=(Complex * p);
		Complex & operator=(int);
		Complex & operator+=(Complex * p);
		Complex * operator*(Complex);
		double getA();
		double getB();
		void setA(double);
		void setB(double);
		friend class Factory;

	private:
		Complex() {
			a = NULL;  //a
			b = NULL;  //b(i)
		}
		double * a; //a
		double * b; //b(i)

};

class ComplexMatrix : public Suicidal, public GenericMatrix {

	public:
		void init(int, int);
		Complex * getVal(int, int);
		void setVal(int, int, Complex *);

	private:
		Complex * array[2][2];
		void testPair(int, int);

};

class RealMatrix : public Suicidal, public GenericMatrix {

	public:
		void init(int, int);
		double getVal(int, int);
		double getInd(int);
		void setInd(int, double);
		void setVal(int, int, double);
		void setArray(double [4][4]);

	private:
		double * array[4][4];
		void testPair(int, int);

};

class Quaternion : public Suicidal {

	public:
		RealMatrix * getRealMatrix();
		ComplexMatrix * getComplexMatrix();
		void setRealMatrix(RealMatrix *);
		void setComplexMatrix(ComplexMatrix *);
		void displayRM();
		void displayCM();

	private:
		RealMatrix * realMatrix;
		ComplexMatrix * complexMatrix;

};

void Suicidal::die() {
	delete this;
}

RealMatrix * Quaternion::getRealMatrix() {
	return this->realMatrix;
}

void Quaternion::setRealMatrix(RealMatrix * incoming) {
	(this->realMatrix) = Factory::copyRealMatrix(incoming);
}

void Quaternion::setComplexMatrix(ComplexMatrix * p) {
	(this->complexMatrix) = Factory::copyComplexMatrix(p);
}

void Quaternion::displayRM() {
	for(int i = 0; i < 4; i++) {
			for(int j = 0; j < 4; j++) {
					if (this->realMatrix->getVal(i, j) < 0)
						cout << this->realMatrix->getVal(i, j) << " ";
					else
						cout << " " << this->realMatrix->getVal(i, j) << " ";
				}
			cout << endl;
		}
}

ComplexMatrix * Quaternion::getComplexMatrix() {
	return this->complexMatrix;
}

void RealMatrix::init(int i, int j) {
	this->array[i][j] = new double;
	*(this->array[i][j]) = 0;
}

void ComplexMatrix::init(int i, int j) {
	this->array[i][j] = Factory::createNewComplex();
	*(this->array[i][j]) = 0;
}

void ComplexMatrix::testPair(int i, int j) {
	if ((i < 0) || (i > 3) || (j < 0) || (j > 3))
		cerr << " testPair ERROR";
}

Complex * ComplexMatrix::getVal(int i, int j) {
	this->testPair(i, j);
	return this->array[i][j];
}

void ComplexMatrix::setVal(int i, int j, Complex * p) {
	this->testPair(i, j);
	*(this->array[i][j]) = *p;
}

ComplexMatrix * Factory::createNewComplexMatrix() {
	ComplexMatrix * temp = new ComplexMatrix();
	for (int i = 0; i < 2; i++) {
			for (int j = 0; j < 2; j++) {
					temp->init(i, j);
					//temp->array[i][j] = Factory::createNewComplex();
					//*(temp->array[i][j]) = 0;
				}
		}
	return temp;
}

double RealMatrix::getVal(int i, int j) {
	testPair(i, j);
	return *(this->array[i][j]);
}

double RealMatrix::getInd(int i) { //<<<<<<CHECK ROW/COLUMN>>>>>>>>>>>
	return this->getVal(0, i);
}

void RealMatrix::setInd(int i, double in) {
	this->setVal(0, 1, in);
}

void RealMatrix::setVal(int i, int j, double newVal) {
	testPair(i, j);
	*(this->array[i][j]) = newVal;
}

void RealMatrix::testPair(int i, int j) {
	if ((i < 0) || (i > 3) || (j < 0) || (j > 3))
		cerr << " testPair ERROR";
}

void RealMatrix::setArray(double incoming[4][4]) {
	for(int i = 0; i < 4; i++)
		for (int j = 0; j < 4; j++)
			this->setVal(i, j, (incoming[i][j]));
}

RealMatrix * Factory::createNewRealMatrix() {
	RealMatrix * temp = new RealMatrix();
	for (int i = 0; i < 4; i++) {
			for (int j = 0; j < 4; j++) {
					temp->init(i, j);
				}
		}
	return temp;
}

Quaternion * Factory::createMatrixProduct(Quaternion * factor1, Quaternion * factor2) {
	Quaternion * temp = Factory::createFromRealMatrix(createMatrixProduct(factor1->getRealMatrix(), factor2->getRealMatrix()));
	return temp;
}

RealMatrix * Factory::copyRealMatrix(RealMatrix * incoming) {
	RealMatrix * temp = Factory::createNewRealMatrix();
	for (int i = 0; i < 4; i++) {
			for (int j = 0; j < 4; j++) {
					temp->setVal(i, j, incoming->getVal(i, j));
				}
		}
	return temp;
}

Quaternion * Factory::createNewQuaternion() {
	return new Quaternion();
}

ComplexMatrix * Factory::copyComplexMatrix(ComplexMatrix * p) {
	ComplexMatrix * temp = Factory::createNewComplexMatrix();
	for (int i = 0; i < 2; i++) {
			for (int j = 0; j < 2; j++) {
					temp->setVal(i, j, p->getVal(i, j));
				}
		}
	return temp;
}

Quaternion * Factory::createFromRealMatrix(RealMatrix * incoming) {
	Quaternion * temp = Factory::createNewQuaternion();
	temp->setRealMatrix(Factory::copyRealMatrix(incoming));
	return Factory::updateQuaternion(temp);
}

Quaternion * Factory::createFromValues(double a, double b, double c, double d) {
	//double na = -1 * a;
	double nb = -1 * b;
	double nc = -1 * c;
	double nd = -1 * d;
	double array[4][4] = {{a, b, c, d}, {nb, a, nd, c}, {nc, d, a, nb}, {nd, nc, b, a}};
	RealMatrix * rm_temp = Factory::createNewRealMatrix();
	rm_temp->setArray(array);
	Quaternion * q_temp = Factory::createFromRealMatrix(rm_temp);
	return q_temp;
}

RealMatrix * Factory::createMatrixProduct(RealMatrix * factor1, RealMatrix * factor2) {
	RealMatrix * product = Factory::createNewRealMatrix();
	double * cellVal = 0;
	for(int producti = 0; producti < 4; producti++) { //row
			for(int productj = 0; productj < 4; productj++) { //column
					cellVal = new double;
					*cellVal = 0;
					for(int i = 0; i < 4; i++) //inner
						*cellVal += (factor1->getVal(producti, i)) * (factor2->getVal(i, productj));
					product->setVal(producti, productj, *cellVal);
					delete cellVal;
				}
		}
	return product;
};

ostream & operator << (ostream& output, Complex * p) {
	//output << p->getA() << " + " << p->getB() << "i";
	if (p->getA() > 0)
		output << " ";
	output << p->getA() << " + ";
	if (p->getB() > 0)
		output << " ";
	output << p->getB() << "i";
	return output;
}

Complex * Factory::createNewComplex() {
	Complex * temp = new Complex();
	temp->setA(0);
	temp->setB(0);
	return temp;
}

Complex * Factory::createNewComplex(double a, double b) {
	Complex * temp = Factory::createNewComplex();
	temp->setA(a);
	temp->setB(b);
	return temp;
}

Complex * Factory::copyComplex(Complex * p) {
	Complex * temp = Factory::createNewComplex();
	*temp = *p;
	return temp;
}

Quaternion * Factory::updateQuaternion(Quaternion * p) {
	//Quaternion * temp = Factory::createNewQuaternion();
	RealMatrix * itsRealMatrix = p->getRealMatrix();
	//ComplexMatrix * itsComplexMatrix;
	ComplexMatrix * tempComplexMatrix = //p->getComplexMatrix();
		Factory::createNewComplexMatrix();
	/*if (itsComplexMatrix == 0)
		;//itsComplexMatrix = Factory::createNewComplexMatrix();*/
	//Quaternion * temp = Factory::create
	Quaternion * temp = Factory::createNewQuaternion();
	temp->setRealMatrix(Factory::copyRealMatrix(p->getRealMatrix()));
	double a = itsRealMatrix->getVal(0, 0);
	double b = itsRealMatrix->getVal(0, 1);
	double c = itsRealMatrix->getVal(0, 2);
	double d = itsRealMatrix->getVal(0, 3);
	tempComplexMatrix->setVal(0, 0, Factory::createNewComplex(a, b));
	tempComplexMatrix->setVal(0, 1, Factory::createNewComplex(c, d));
	tempComplexMatrix->setVal(1, 0, Factory::createNewComplex((-1 * c), d));
	tempComplexMatrix->setVal(1, 1, Factory::createNewComplex(a, (-1 * b)));
	//return Factory::createNewQuaternion();
	//temp->setRealMatrix(itsRealMatrix);
	temp->setComplexMatrix(tempComplexMatrix);
	return temp;
}

double Complex::getA() {
	return *(this->a);
}

double Complex::getB() {
	return *(this->b);
}

void Complex::setA(double newA) {
	if (this->a != 0)
		delete this->a;
	this->a = new double;
	*(this->a) = newA;
}

void Complex::setB(double newB) {
	if (this->b != 0)
		delete this->b;
	this->b = new double;
	*(this->b) = newB;
}

Complex & Complex::operator=(Complex * p) {
	this->setA(p->getA());
	this->setB(p->getB());
	return *this;
}

Complex & Complex::operator=(int p) {
	if (p == 0) {
			;
			this->setA(0);
			this->setB(0);
		}
	else {
			cerr << "Operator <complex> = 0 error" << endl;
		}
	return *this;
};

Complex & Complex::operator+=(Complex * p) {
	if (this != p) {
			this->setA(this->getA() + p->getA());
			this->setB(this->getB() + p->getB());
		}
	return *this;
}

Complex * Complex::operator*(Complex p) {
	double * x = new double;
	double * y = new double;
	double * u = new double;
	double * v = new double;
	double * coreal = new double;
	double * coim = new double;
	*x = this->getA();
	*y = this->getB();
	*u = p.getA();
	*v = p.getB();
	*coreal = ((*x) * (*u)) - ((*y) * (*v));
	*coim   = ((*x) * (*v)) + ((*y) * (*u));
	Complex * temp = Factory::createNewComplex(*coreal, *coim);
	delete x;
	delete y;
	delete u;
	delete u;
	delete coreal;
	delete coim;
	return temp;
}

void Quaternion::displayCM() {
	for(int i = 0; i < 2; i++) {
			for(int j = 0; j < 2; j++) {
					if (this->complexMatrix->getVal(i, j) < 0)
						cout << this->complexMatrix->getVal(i, j) << " ";
					else
						cout << " " << this->complexMatrix->getVal(i, j) << " ";
				}
			cout << endl;
		}
}

Quaternion * Factory::createFormulaProduct(Quaternion * h1, Quaternion * h2) {
	Quaternion * qtemp/* = Factory::createNewQuaternion()*/;
	double a1 = h1->getRealMatrix()->getInd(0);
	double a2 = h2->getRealMatrix()->getInd(0);
	double b1 = h1->getRealMatrix()->getInd(1);
	double b2 = h2->getRealMatrix()->getInd(1);
	double c1 = h1->getRealMatrix()->getInd(2);
	double c2 = h2->getRealMatrix()->getInd(2);
	double d1 = h1->getRealMatrix()->getInd(3);
	double d2 = h2->getRealMatrix()->getInd(3);
	qtemp = Factory::createFromValues((a1 * a2 - b1 * b2 - c1 * c2 - d1 * d2),
									  (a1 * b2 + b1 * a2 + c1 * d2 - d1 * c2),
									  (a1 * c2 - b1 * d2 + c1 * a2 + d1 * b2),
									  (a1 * d2 - b1 * c2 - c1 * b2 + d1 * a2));
	return qtemp;
}

ComplexMatrix * Factory::createCMProduct(ComplexMatrix * factor1, ComplexMatrix * factor2) {
	ComplexMatrix * product = Factory::createNewComplexMatrix();
	Complex * cellVal = 0;
	for(int producti = 0; producti < 2; producti++) { //row
			for(int productj = 0; productj < 2; productj++) { //column
					cellVal = Factory::createNewComplex();
					*cellVal = 0;
					for(int i = 0; i < 2; i++)//inner
						*cellVal += (*(factor1->getVal(producti, i))) * (*(factor2->getVal(i, productj)));
					product->setVal(producti, productj, cellVal);
					delete cellVal;
				}
		}
	return product;
}

Quaternion * Factory::createCMProduct(Quaternion * factor1, Quaternion * factor2) {
	Quaternion * temp = Factory::createNewQuaternion();
	ComplexMatrix * matrix1 = Factory::copyComplexMatrix(factor1->getComplexMatrix());
	ComplexMatrix * matrix2 = Factory::copyComplexMatrix(factor2->getComplexMatrix());
	ComplexMatrix * product = Factory::createCMProduct(matrix1, matrix2);
	temp->setComplexMatrix(product);
	return temp;
}

/*reference
Quaternion * Factory::createMatrixProduct(Quaternion * factor1, Quaternion * factor2) {
	Quaternion * temp = Factory::createFromRealMatrix(createMatrixProduct(factor1->getRealMatrix(), factor2->getRealMatrix()));
	return temp;
}
*/

int main(int argc, char* argv[]) {

#ifdef quaternion_debug_complex

	/*//clock_t t;
	//t = clock();
	//Quaternion * myQuaternion;
	for(int trial = 0; trial < 1; trial++){
	    for(long i = 0; i < 500000; i++){
	        Quaternion * myQuaternion1 = Factory::createFromValues(i,i+1,i+2,i+3);
	        Quaternion * myQuaternion2 = Factory::createFromValues(2*i,2*i+1,2*i+2,2*i+3);
	        myQuaternion = Factory::createMatrixProduct(myQuaternion1, myQuaternion2);
	        t = clock() - t;
	        //myQuaternion = Factory::updateQuaternion(myQuaternion1);
	        //myQuaternion->displayRM();
	        myQuaternion->die();
	        myQuaternion1->die();
	        myQuaternion2->die();
	    }
	    cout<<((float)t)/CLOCKS_PER_SEC<<" seconds.";
	}*/
	Quaternion * myQuaternion1 = Factory::createFromValues(1, 2, 3, 4);
	Quaternion * myQuaternion2;// = Factory::createFromValues(5,6,7,8);
	Quaternion * myQuaternion3;
	Quaternion * myQuaternion5 = Factory::createFromValues(5, 6, 7, 8);
	Quaternion * testq = Factory::createNewQuaternion();
	myQuaternion2 = Factory::updateQuaternion(myQuaternion1);
	myQuaternion3 = Factory::updateQuaternion(myQuaternion5);
	ComplexMatrix * cm2 = myQuaternion1->getComplexMatrix();
	ComplexMatrix * cm3 = myQuaternion5->getComplexMatrix();
	ComplexMatrix * product = Factory::createCMProduct(cm2, cm3);
	testq->setComplexMatrix(product);
	testq->displayCM();
	//cout<<endl;
	//myQuaternion2->displayCM();
	//Quaternion * myQuaternion2 = Factory::createFromValues(2, 3, 4, 5);
	//cout<<"mq1"<<endl;
	//myQuaternion1->displayRM();
	//cout<<endl<<"my2"<<endl;
	//myQuaternion2->displayRM();

#else //quaternion_debug_complex

	Quaternion * myQ1 = Factory::createFromValues(1, 2, 3, 4);
	Quaternion * myQ2 = Factory::createFromValues(2, 3, 4, 5);
	Quaternion * product;
	//cout<<endl<<"matrix product:"<<endl;
	//product = Factory::createMatrixProduct(myQ1, myQ2);
	//product->displayRM();
	//product->die();
	//cout<<endl<<"formula product:"<<endl;
	//product = Factory::createFormulaProduct(myQ1, myQ2);
	//product->displayRM();
	clock_t t;
	t = clock();
	Quaternion * myQuaternion;

#ifdef quaternion_testingtimenotmemory

	for(float trial = 0; trial < 25; trial += (0.5)) { //-------------------------time-----------------------------
			for(long i = (trial * 1000); i < (trial * 2048); i++) { //<<<<<<<<<<<>>>>>>>>>>>
					Quaternion * myQ1 = Factory::createFromValues(i, i + 1, i + 2, i + 3);
					Quaternion * myQ2 = Factory::createFromValues(2 * i, 2 * i + 1, 2 * i + 2, 2 * i + 3);
					myQuaternion = Factory::createFormulaProduct(myQ1, myQ2);
					//t = clock() - t;
					//myQuaternion = Factory::updateQuaternion(myQuaternion1);
					//myQuaternion->displayRM();
					myQuaternion->die();
					myQ1->die();
					myQ2->die();
				}
			t = clock() - t;
			cout << trial * 2 << ", " << ((float)t) / CLOCKS_PER_SEC << endl;
		}
	product->die();

#else //quaternion_testingtimenotmemory

	const int trialc = 0; //vary this for mem testing in multiples of 64
	for(int trial = trialc; trial < (trialc + 1); trial++) {//---------------------------memory---------------------------
			for(long i = (trial * 1024); i < (trial * 2560); i++) { //<<<<<<<<<<<>>>>>>>>>>>
					Quaternion * myQ1 = Factory::createFromValues(i, i + 1, i + 2, i + 3);
					Quaternion * myQ2 = Factory::createFromValues(2 * i, 2 * i + 1, 2 * i + 2, 2 * i + 3);
					Quaternion * myQuaternion = Factory::createFormulaProduct(myQ1, myQ2);
					//t = clock() - t;
					//myQuaternion = Factory::updateQuaternion(myQuaternion1);
					//myQuaternion->displayRM();
					myQuaternion->die();
					myQ1->die();
					myQ2->die();
				}
			system("pause");
			t = clock() - t;
			cout << "i = " << trial << " : " << ((float)t) / CLOCKS_PER_SEC << " seconds." << endl;
		}
	product->die();

#endif //quaternion_testingtimenotmemory

#endif //quaternion_debug_complex

	return 0;

}
